package org.spa.common.aspect;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.PropertyFilter;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.spa.common.aspect.annotation.AutoLog;
import org.spa.common.constant.CommonConstant;
import org.spa.common.constant.enums.ModuleType;
import org.spa.common.util.IPUtils;
import org.spa.common.util.SpringContextUtils;
import org.spa.modules.base.entity.SysLog;
import org.spa.modules.base.service.SysLogService;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * 系统日志，切面处理类
 *
 * @Author scott
 * @email jeecgos@163.com
 * @Date 2018年1月14日
 */
@Aspect
@Component
public class AutoLogAspect {
	@Resource
	private SysLogService sysLogService;

	@Pointcut("@annotation(org.spa.common.aspect.annotation.AutoLog)")
	public void logPointCut() {

	}

	@Around("logPointCut()")
	public Object around(ProceedingJoinPoint point) throws Throwable {
		long beginTime = System.currentTimeMillis();
		// 执行方法
		Object result = point.proceed();
		// 执行时长(毫秒)
		long time = System.currentTimeMillis() - beginTime;

		// 保存日志
		saveSysLog(point, time, result);

		return result;
	}

	private void saveSysLog(ProceedingJoinPoint joinPoint, long time, Object obj) {
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		Method method = signature.getMethod();

		SysLog dto = new SysLog();
		AutoLog syslog = method.getAnnotation(AutoLog.class);
		if (syslog != null) {
			// update-begin-author:taoyan date:
			String content = syslog.value();
			// 注解上的描述,操作日志内容
			dto.setLogType(syslog.logType());
			dto.setLogContent(content);
		}

		// 请求的方法名
		String className = joinPoint.getTarget().getClass().getName();
		String methodName = signature.getName();
		dto.setMethod(className + "." + methodName + "()");

		// 设置操作类型
		if (dto.getLogType() == CommonConstant.LOG_TYPE_2) {
			dto.setOperateType(getOperateType(methodName, syslog.operateType()));
		}

		// 获取request
		HttpServletRequest request = SpringContextUtils.getHttpServletRequest();
		// 请求的参数
		dto.setRequestParam(getReqestParams(request, joinPoint));
		// 设置IP地址
		dto.setIp(IPUtils.getIpAddr(request));
		// 获取登录用户信息
//		LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
//		if (sysUser != null) {
//			dto.setUserid(sysUser.getUsername());
//			dto.setUsername(sysUser.getRealname());
//
//		}
		// 耗时
		dto.setCostTime(time);
		dto.setCreateTime(new Date());
		// 保存系统日志
		sysLogService.insert(dto);
	}

	/**
	 * 获取操作类型
	 */
	private int getOperateType(String methodName, int operateType) {
		if (operateType > 0) {
			return operateType;
		}
		if (methodName.startsWith("list")) {
			return CommonConstant.OPERATE_TYPE_1;
		}
		if (methodName.startsWith("add")) {
			return CommonConstant.OPERATE_TYPE_2;
		}
		if (methodName.startsWith("edit")) {
			return CommonConstant.OPERATE_TYPE_3;
		}
		if (methodName.startsWith("delete")) {
			return CommonConstant.OPERATE_TYPE_4;
		}
		if (methodName.startsWith("import")) {
			return CommonConstant.OPERATE_TYPE_5;
		}
		if (methodName.startsWith("export")) {
			return CommonConstant.OPERATE_TYPE_6;
		}
		return CommonConstant.OPERATE_TYPE_1;
	}

	/**
	 * @Description: 获取请求参数
	 * @author: scott
	 * @date: 2020/4/16 0:10
	 * @param request:   request
	 * @param joinPoint: joinPoint
	 * @Return: java.lang.String
	 */
	private String getReqestParams(HttpServletRequest request, JoinPoint joinPoint) {
		String httpMethod = request.getMethod();
		String params = "";
		if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) || "PATCH".equals(httpMethod)) {
			Object[] paramsArray = joinPoint.getArgs();
			// java.lang.IllegalStateException: It is illegal to call this method if the
			// current request is not in asynchronous mode (i.e. isAsyncStarted() returns
			// false)
			// https://my.oschina.net/mengzhang6/blog/2395893
			Object[] arguments = new Object[paramsArray.length];
			for (int i = 0; i < paramsArray.length; i++) {
				if (paramsArray[i] instanceof BindingResult || paramsArray[i] instanceof ServletRequest
						|| paramsArray[i] instanceof ServletResponse || paramsArray[i] instanceof MultipartFile) {
					// ServletRequest不能序列化，从入参里排除，否则报异常：java.lang.IllegalStateException: It is
					// illegal to call this method if the current request is not in asynchronous
					// mode (i.e. isAsyncStarted() returns false)
					// ServletResponse不能序列化 从入参里排除，否则报异常：java.lang.IllegalStateException:
					// getOutputStream() has already been called for this response
					continue;
				}
				arguments[i] = paramsArray[i];
			}
			// update-begin-author:taoyan date:20200724 for:日志数据太长的直接过滤掉
			PropertyFilter profilter = new PropertyFilter() {
				@Override
				public boolean apply(Object o, String name, Object value) {
					if (value != null && value.toString().length() > 500) {
						return false;
					}
					return true;
				}
			};
			params = JSONObject.toJSONString(arguments, profilter);
			// update-end-author:taoyan date:20200724 for:日志数据太长的直接过滤掉
		} else {
			MethodSignature signature = (MethodSignature) joinPoint.getSignature();
			Method method = signature.getMethod();
			// 请求的方法参数值
			Object[] args = joinPoint.getArgs();
			// 请求的方法参数名称
			LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
			String[] paramNames = u.getParameterNames(method);
			if (args != null && paramNames != null) {
				for (int i = 0; i < args.length; i++) {
					params += "  " + paramNames[i] + ": " + args[i];
				}
			}
		}
		return params;
	}
}
